package top.ezzz.search.model;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.function.Consumer;

/**
 * ElasticSearch 条件构建器
 *
 * @author noear
 * @since 1.0
 */
public class EsCondition {
    private final JSONObject oNode;

    public EsCondition(JSONObject oNode) {
        this.oNode = oNode;
    }

    JSONArray oNodeArray = null;
    String score_mode = null;


    /**
     * 设置过滤风格
     */
    private void filterStyleSet(String name) {
        if (score_mode == null) {
            if (oNode.get("bool") == null) {
                oNode.set("bool", new JSONObject());
            }
            JSONObject bool = oNode.getJSONObject("bool");
            if (bool.get(name) == null) {
                bool.set(name, new JSONArray());
            }
            oNodeArray = bool.getJSONArray(name);
        } else {
            // 使用评分模式
            if (oNode.get("function_score") == null) {
                oNode.set("function_score", new JSONObject());
            }

            JSONObject function_score = oNode.getJSONObject("function_score");
            if (function_score.get("query") == null) {
                function_score.set("query", new JSONObject());
            }

            JSONObject query = function_score.getJSONObject("query");
            if (query.get("bool") == null) {
                query.set("bool", new JSONObject());
            }

            JSONObject bool = query.getJSONObject("bool");
            if (bool.get(name) == null) {
                bool.set(name, new JSONArray());
            }
            oNodeArray = bool.getJSONArray(name);
            if (score_mode.length() > 0) {
                if (oNode.get("function_score") == null) {
                    oNode.set("function_score", JSONUtil.createObj());
                }
                oNode.getJSONObject("function_score").set("score_mode", score_mode);
            }
        }
    }

    /**
     * 设置过滤风格
     */
    private void filterSet(String type, String field, Object value) {
        if (oNodeArray == null) {
            if (score_mode == null) {
                if (oNode.get(type) == null) {
                    oNode.set(type, JSONUtil.createObj());
                }
                oNode.getJSONObject(type).set(field, value);
            } else {
                // 使用评分模式
                if (oNode.get("function_score") == null) {
                    oNode.set("function_score", JSONUtil.createObj());
                }

                JSONObject function_score = oNode.getJSONObject("function_score");
                if (function_score.get("query") == null) {
                    function_score.set("query", JSONUtil.createObj());
                }

                JSONObject query = function_score.getJSONObject("query");
                if (query.get(type) == null) {
                    query.set(type, JSONUtil.createObj());
                }

                JSONObject typeObj = query.getJSONObject(type);
                typeObj.set(field, value);
                if (score_mode.length() > 0) {
                    if (oNode.get("function_score") == null) {
                        oNode.set("function_score", JSONUtil.createObj());
                    }
                    oNode.getJSONObject("function_score").set("score_mode", score_mode);
                }
            }
        } else {
            oNodeArray.add(JSONUtil.createObj().set(type, JSONUtil.createObj().set(field, value)));
        }
    }

    /**
     * 启用评分定制
     * <p>
     * function_score/..
     */
    public EsCondition useScore() {
        return useScore(null);
    }

    /**
     * 启用评分定制
     * <p>
     * function_score/..
     */
    public EsCondition useScore(String mode) {
        if (mode == null) {
            score_mode = "";
        } else {
            score_mode = null;
        }

        return this;
    }


    /**
     * 只过滤，不参与打分
     * <p>
     * bool/filter
     */
    public EsCondition filter() {
        filterStyleSet("filter");
        return this;
    }

    /**
     * 如果有多个条件，这些条件都必须满足 and与
     * <p>
     * bool/must
     */
    public EsCondition must() {
        filterStyleSet("must");
        return this;
    }

    /**
     * 如果有多个条件，满足一个或多个即可 or或
     * <p>
     * bool/should
     */
    public EsCondition should() {
        filterStyleSet("should");
        return this;
    }

    /**
     * 设置should条件最小匹配数
     * <p>
     * bool/minimum_should_match
     */
    public EsCondition minimumShouldMatch(int min) {
        if (oNode.get("bool") == null) {
            oNode.set("bool", JSONUtil.createObj());
        }
        oNode.getJSONObject("bool").set("minimum_should_match", min);
        return this;
    }

    /**
     * 和must相反，必须都不满足条件才可以匹配到 ！非
     * <p>
     * bool/mustNot
     */
    public EsCondition mustNot() {
        filterStyleSet("must_not");
        return this;
    }


    /**
     * match_all
     */
    public void matchAll() {
        if (oNode.get("match_all") == null) {
            oNode.set("match_all", JSONUtil.createObj());
        }
        oNode.get("match_all");
    }


    /**
     * nested（nested类型的内嵌对象）
     */
    public EsCondition nested(String path, Consumer<EsCondition> condition) {
        if (oNodeArray == null) {
            oNodeArray = JSONUtil.createArray();
        }
        JSONObject oNode1 = JSONUtil.createObj();
        EsCondition c = new EsCondition(oNode1);
        condition.accept(c);
        JSONObject nested = JSONUtil.createObj().set("nested", JSONUtil.createObj().set("path", path).set("query", oNode1));
        oNodeArray.add(nested);

        //modify by @shenmk
        if (oNode.get("bool") == null) {
            oNode.set("bool", JSONUtil.createObj());
        }
        JSONObject bool = oNode.getJSONObject("bool");
        bool.set("must", oNodeArray);
        return this;
    }

    /**
     * match
     */
    public EsCondition match(String field, Object value) {
        filterSet("match", field, value);
        return this;
    }

    /**
     * match
     */
    public EsCondition matchIf(boolean condition, String field, Object value) {
        return condition ? match(field, value) : this;
    }

    /**
     * match_phrase
     */
    public EsCondition matchPhrase(String field, Object value) {
        filterSet("match_phrase", field, value);
        return this;
    }

    /**
     * match_phrase
     */
    public EsCondition matchPhraseIf(boolean condition, String field, Object value) {
        return condition ? matchPhrase(field, value) : this;
    }

    /**
     * match_phrase slop
     */
    public EsCondition matchPhrase(String field, Object value, int slop) {
        JSONObject oNode = JSONUtil.createObj();
        oNode.set("query", value);
        oNode.set("slop", slop);
        filterSet("match_phrase", field, oNode);
        return this;
    }

    /**
     * match_phrase slop
     */
    public EsCondition matchPhraseIf(boolean condition, String field, Object value, int slop) {
        return condition ? matchPhrase(field, value, slop) : this;
    }

    /**
     * match_phrase_prefix
     */
    public EsCondition matchPhrasePrefix(String field, Object value) {
        filterSet("match_phrase_prefix", field, value);
        return this;
    }

    /**
     * match_phrase_prefix
     */
    public EsCondition matchPhrasePrefixIf(boolean condition, String field, Object value) {
        return condition ? matchPhrasePrefix(field, value) : this;
    }

    /**
     * match_phrase_prefix slop
     */
    public EsCondition matchPhrasePrefix(String field, Object value, int slop) {
        JSONObject oNode = JSONUtil.createObj();
        oNode.set("query", value);
        oNode.set("slop", slop);

        filterSet("match_phrase_prefix", field, oNode);
        return this;
    }

    /**
     * match_phrase_prefix slop
     */
    public EsCondition matchPhrasePrefixIf(boolean condition, String field, Object value, int slop) {
        return condition ? matchPhrasePrefix(field, value, slop) : this;
    }

    /**
     * exists
     */
    public EsCondition exists(String field) {
        filterSet("exists", "field", field);
        return this;
    }

    /**
     * exists
     */
    public EsCondition existsIf(boolean condition, String field) {
        return condition ? exists(field) : this;
    }

    /**
     * term
     */
    public EsCondition term(String field, Object value) {
        filterSet("term", field, value);
        return this;
    }

    /**
     * term
     */
    public EsCondition termIf(boolean condition, String field, Object value) {
        return condition ? term(field, value) : this;
    }

    /**
     * terms
     */
    public EsCondition terms(String field, Object... values) {
        JSONArray array = JSONUtil.createArray();
        array.addAll(Arrays.asList(values));
        filterSet("terms", field, array);
        return this;
    }

    /**
     * terms
     */
    public EsCondition terms(String field, Collection<?> values) {
        JSONArray array = JSONUtil.createArray();
        array.addAll(Collections.singletonList(values));
        filterSet("terms", field, array);
        return this;
    }

    /**
     * terms
     */
    public EsCondition termsIf(boolean condition, String field, Object... values) {
        return condition ? terms(field, (Object[]) values) : this;
    }

    /**
     * terms
     */
    public EsCondition termsIf(boolean condition, String field, Collection<?> values) {
        return condition ? terms(field, values) : this;
    }

    /**
     * range
     */
    public EsCondition range(String field, Consumer<EsRange> range) {
        JSONObject oNode1 = JSONUtil.createObj();
        EsRange r = new EsRange(oNode1);
        range.accept(r);
        filterSet("range", field, oNode1);
        return this;
    }

    /**
     * range
     */
    public EsCondition rangeIf(boolean condition, String field, Consumer<EsRange> range) {
        return condition ? range(field, range) : this;
    }


    /**
     * prefix
     */
    public EsCondition prefix(String field, String value) {
        filterSet("prefix", field, value);
        return this;
    }

    /**
     * prefix
     */
    public EsCondition prefixIf(boolean condition, String field, String value) {
        return condition ? prefix(field, value) : this;
    }

    /**
     * wildcard
     *
     * @param value *表示任意字符，?表示任意单个字符(
     */
    public EsCondition wildcard(String field, String value) {
        filterSet("wildcard", field, value);
        return this;
    }

    /**
     * wildcard
     *
     * @param value *表示任意字符，?表示任意单个字符(
     */
    public EsCondition wildcardIf(boolean condition, String field, String value) {
        return condition ? wildcard(field, value) : this;
    }

    /**
     * regexp
     */
    public EsCondition regexp(String field, String value) {
        filterSet("regexp", field, value);
        return this;
    }

    /**
     * regexp
     */
    public EsCondition regexpIf(boolean condition, String field, String value) {
        return condition ? regexp(field, value) : this;
    }

    /**
     * script
     */
    public EsCondition script(String source, Consumer<EsMap> params) {
        return script(source, "painless", params);
    }

    /**
     * script
     */
    public EsCondition script(String source, String lang, Consumer<EsMap> params) {
        EsMap param = new EsMap();
        params.accept(param);
        JSONObject oNode1 = JSONUtil.createObj();
        oNode1.set("source", source);
        oNode1.set("lang", lang);
        if (param.size() > 0) {
            if (oNode1.get("params") == null) {
                oNode1.set("params", JSONUtil.createObj());
            }
            oNode1.getJSONObject("params").putAll(param);
        }
        filterSet("script", "script", oNode1);
        return this;
    }


    /**
     * 添加下级条件
     */
    public EsCondition add(Consumer<EsCondition> condition) {
        if (oNodeArray == null) {
            throw new IllegalArgumentException("Conditions lack combination types");
        }
        EsCondition c = new EsCondition(JSONUtil.createObj());
        condition.accept(c);
        return this;
    }
}
